package com.zsr.xmall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.zsr.common.constant.product.CategoryConstant;
import com.zsr.common.entity.product.CategoryEntity;
import com.zsr.common.vo.product.Catalog2VO;
import com.zsr.xmall.product.service.CategoryBrandRelationService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zsr.common.utils.PageUtils;
import com.zsr.common.utils.Query;

import com.zsr.xmall.product.dao.CategoryDao;
import com.zsr.xmall.product.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    private CategoryBrandRelationService categoryBrandRelationService;

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    RedissonClient redisson;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    /**
     * 构建树形结构
     *
     * @return
     */
    @Override
    public List<CategoryEntity> listWithTree() {
        List<CategoryEntity> list = baseMapper.selectList(null);
        //  构建树形结构
        Map<Long, List<CategoryEntity>> map = list
                .stream().collect(Collectors.groupingBy(CategoryEntity::getParentCid, LinkedHashMap::new, Collectors.toList()));
        List<CategoryEntity> resultList = new ArrayList<>();
        //根节点
        List<CategoryEntity> level1Menus = map.get(0L);
        if (!CollectionUtils.isEmpty(level1Menus)) {
            level1Menus.sort(Comparator.comparingInt(CategoryEntity::getSort));
            for (CategoryEntity item : level1Menus) {
                setChildrenNode(map, item);
                resultList.add(item);
            }
        }
        return resultList;
    }

    private void setChildrenNode(Map<Long, List<CategoryEntity>> map, CategoryEntity categoryEntity) {
        List<CategoryEntity> children = map.get(categoryEntity.getCatId());
        if (!CollectionUtils.isEmpty(children)) {
            children.sort(Comparator.comparingInt(CategoryEntity::getSort));
            categoryEntity.setChildren(children);
            for (CategoryEntity item : children) {
                setChildrenNode(map, item);
            }
        }
    }

    @Override
    public void removeMenuByIds(List<Long> asList) {
        //TODO 1.检查当前删除菜单是否被别的地方引用
        baseMapper.deleteBatchIds(asList);
    }

    @Override
    public Long[] findCatelogPath(Long catelogId) {
        List<Long> paths = new ArrayList<>();
        findParentPath(catelogId, paths);
        Collections.reverse(paths);
        return paths.toArray(new Long[paths.size()]);
    }

    private List<Long> findParentPath(Long cateId, List<Long> paths) {
        paths.add(cateId);
        CategoryEntity entity = this.getById(cateId);
        if (entity.getParentCid() != 0) {
            findParentPath(entity.getParentCid(), paths);
        }
        return paths;
    }


    @Transactional
    @Override
    //@CacheEvict(value = {"category"},key = "'getLevel1Categorys'")//清空缓存
    /*@Caching(evict = {
            @CacheEvict(value = {"category"},key = "'getLevel1Categorys'"),
            @CacheEvict(value = {"category"},key = "'getCatalogJson'")
    })*/
    @CacheEvict(value = {"category"}, allEntries = true)//删除category分区下的数据
    public void updateCascade(CategoryEntity category) {

        this.updateById(category);
        categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
    }

    @Override
    @Cacheable(value = {"category"},key = "#root.method.name")//spEL表达式
    public List<CategoryEntity> getLevel1Categorys() {
        // 查询父id=0
        return baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
    }

    /**
     * 获取嵌套分类
     *  sync = true 同步 加锁。解决缓存击穿问题
     * @return
     */
    @Override
    @Cacheable(value = {"category"}, key = "'getCatalogJson'", sync = true)
    public Map<String, List<Catalog2VO>> getCatalogJsonWithSpringCache() {
        return getCatalogDB();
    }

    /**
     * 测试分布式锁 入口方法
     * @return
     */
    public Map<String, List<Catalog2VO>> getCatalogJson() {
        String catlogJSON = redisTemplate.opsForValue().get(CategoryConstant.CACHE_KEY_CATALOG_JSON);
        if (StringUtils.isEmpty(catlogJSON)) {
            System.out.println("缓存未命中...将要查询数据库");
            return getCatalogJsonFromDBWithRedisLock();
        }

        //return getCatalogJsonFromDB();
        System.out.println("缓存已命中...");
        // 查询成功直接返回不需要查询DB
        Map<String, List<Catalog2VO>> result = JSON.parseObject(catlogJSON, new TypeReference<Map<String, List<Catalog2VO>>>() {
        });
        return result;
    }



    /**
     * 分布式锁 redisson 版本
     *  问题： 缓存数据一致性问题（修改缓存的db数据时，缓存中不是最新的）
     *  1）双写模式：
     *  2）失效模式：
     *  都会导致数据不一致
     * @return
     */
    public Map<String, List<Catalog2VO>> getCatalogJsonFromDBWithRedissonLock() {

        RLock lock = redisson.getLock("catalogJson-lock");
        lock.lock();
        Map<String, List<Catalog2VO>> catalogJsonFromDB;
        try {
            catalogJsonFromDB = getCatalogJsonFromDB();
        } finally {
            lock.unlock();
        }

        return catalogJsonFromDB;

    }

    /**
     * 使用redis实现分部式锁【原生版】
     * <p>
     * 问题1：如果执行业务出现异常或者宕机，导致没发删除锁，其他服务 无法执行
     * 解决1：设置过期时间
     * <p>
     * 问题2：设置过期前可能会宕机
     * 解决2：在获set的时候加入过期时间
     * <p>
     * 问题3：业务超时，导致当前线程锁过期，后面删除锁会将其他人的锁删除
     * 解决3：占锁的时候，值设定为uuid   ，每个人匹配是自己的锁才删除
     * <p>
     * 问题4：获取uuid值对比 和 对比成功不是原子操作。 还是会删除别人的锁  （返回redis值的时候正好过期，对比成功删除别人的说）
     * 解决4：使用lua脚本 使其成为原子性操作
     *
     * @return
     */
    public Map<String, List<Catalog2VO>> getCatalogJsonFromDBWithRedisLock() {
        String uuid = UUID.randomUUID().toString();
        //1.分布式锁 去redis占坑
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 30, TimeUnit.SECONDS);
        if (lock != null && lock) {
            System.out.println("获取分布式锁成功..");

            //设置过期时间 必须是原子的
            //redisTemplate.expire("lock",30,TimeUnit.SECONDS);
            //加锁成功 去执行业务
            Map<String, List<Catalog2VO>> catalogJsonFromDB;
            try {
                catalogJsonFromDB = getCatalogJsonFromDB();
            } finally {
                /*String lockValue = redisTemplate.opsForValue().get("lock");
                if(uuid.equals(lockValue)){
                    //删除锁
                    redisTemplate.delete("lock");
                }*/
                // 查询+删除（当前值与目标值是否相等，相等执行删除，不等返回0，等于返回1）
                String luaScript = "if redis.call('get',KEYS[1]) == ARGV[1]\n" +
                        "then\n" +
                        "    return redis.call('del',KEYS[1])\n" +
                        "else\n" +
                        "    return 0\n" +
                        "end";
                // 删除锁
                redisTemplate.execute(new DefaultRedisScript<Long>(luaScript, Long.class), Collections.singletonList("lock"), uuid);

            }

            return catalogJsonFromDB;
        } else {
            //加锁失败 重试
            //类似 synchronized重试 可以加休眠100ms
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            }
            System.out.println("获取分布式锁失败...重试...");
            return getCatalogJsonFromDBWithRedisLock();
        }
    }

    /**
     * 查询三级分类（本地锁版本）
     * 已废弃
     */
    @Deprecated
    public Map<String, List<Catalog2VO>> getCatalogJsonFromDBWithLocalLock() {
        // 本地锁：synchronized，JUC(lock)，在分布式0情况下，需要使用分布式锁
        synchronized (this) {
            // 得到锁以后还要检查一次，double check
            return getCatalogJsonFromDB();
        }
    }

    /**
     * 1.缓存穿透 :   null结构缓存，并加入短暂过期时间
     * 2.缓存雪崩：  随机失效时间
     * 3.缓存击穿：  加锁，一个一个来，第一个查数据库放入缓存，剩下的直接查缓存
     * <p>
     * 查询三级分类（从数据源DB查询）
     * 加入分布式锁版本代码，double check检查
     */
    public Map<String, List<Catalog2VO>> getCatalogJsonFromDB() {
        // 1.double check，占锁成功需要再次检查缓存
        // 查询非空即返回
        String catlogJSON = redisTemplate.opsForValue().get(CategoryConstant.CACHE_KEY_CATALOG_JSON);
        if (!StringUtils.isEmpty(catlogJSON)) {
            // 查询成功直接返回不需要查询DB
            Map<String, List<Catalog2VO>> result = JSON.parseObject(catlogJSON, new TypeReference<Map<String, List<Catalog2VO>>>() {
            });
            return result;
        }
        System.out.println("查询了数据库...");
        //本地锁 synchronized Lock，但是在分布式情况下，必须使用分布式锁
        Map<String, List<Catalog2VO>> result=getCatalogDB();

        // 5.结果集存入redis
        // 关注锁时序问题，存入redis代码块必须在同步快内执行  否则导致还没放入redis就释放锁，多一次查询数据库
        redisTemplate.opsForValue().set(CategoryConstant.CACHE_KEY_CATALOG_JSON,
                JSONObject.toJSONString(result), 1, TimeUnit.DAYS);

        return result;
    }

    public Map<String, List<Catalog2VO>> getCatalogDB() {

        // 2.查询所有分类，按照parentCid分组
        Map<Long, List<CategoryEntity>> categoryMap = baseMapper.selectList(null).stream()
                .collect(Collectors.groupingBy(key -> key.getParentCid()));

        // 3.获取1级分类
        List<CategoryEntity> level1Categorys = categoryMap.get(0L);

        // 4.封装数据
        Map<String, List<Catalog2VO>> result = level1Categorys.stream().collect(Collectors.toMap(key -> key.getCatId().toString(), l1Category -> {
            // 3.查询2级分类，并封装成List<Catalog2VO>
            List<Catalog2VO> catalog2VOS = categoryMap.get(l1Category.getCatId())
                    .stream().map(l2Category -> {
                        // 4.查询3级分类，并封装成List<Catalog3VO>
                        List<Catalog2VO.Catalog3Vo> catalog3Vos = categoryMap.get(l2Category.getCatId())
                                .stream().map(l3Category -> {
                                    // 封装3级分类VO
                                    Catalog2VO.Catalog3Vo catalog3Vo = new Catalog2VO.Catalog3Vo(l2Category.getCatId().toString(), l3Category.getCatId().toString(), l3Category.getName());
                                    return catalog3Vo;
                                }).collect(Collectors.toList());
                        // 封装2级分类VO返回
                        Catalog2VO catalog2VO = new Catalog2VO(l1Category.getCatId().toString(), catalog3Vos, l2Category.getCatId().toString(), l2Category.getName());
                        return catalog2VO;
                    }).collect(Collectors.toList());
            return catalog2VOS;
        }));

        return result;
    }



}